import nodeConfig from './config'
import { validatenull as isEmpty } from '@/util/validate'

const isEmptyArray = data => (Array.isArray(data) ? data.length === 0 : true)

export class NodeUtils {
  /**
   * 根据自增数生成64进制id
   * @return {string} 64进制id字符串
   */
  static idGenerator() {
    let quotient = new Date() - new Date('2021-05-23')
    quotient += Math.ceil(Math.random() * 1000) // 防止重复
    const chars = '0123456789ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz'
    const charArr = chars.split('')
    const radix = chars.length
    const res = []
    do {
      let mod = quotient % radix
      quotient = (quotient - mod) / radix
      res.push(charArr[mod])
    } while (quotient)
    return res.join('')
  }

  /**
   * 判断节点类型 条件节点
   * @param {object} node - 节点数据
   * @return {boolean}
   * */
  static isConditionNode(node) {
    return node && node.type === 'condition'
  }

  /**
   * 判断节点类型 抄送节点
   * @param {object} node - 节点数据
   * @return {boolean}
   * */
  static isCopyNode(node) {
    return node && node.type === 'copy'
  }

  /**
   * 判断节点类型 起始节点
   * @param {object} node - 节点数据
   * @return {boolean}
   * */
  static isStartNode(node) {
    return node && node.type === 'start'
  }

  /**
   * 判断节点类型 审批节点
   * @param {object} node - 节点数据
   * @return {boolean}
   * */
  static isApproverNode(node) {
    return node && node.type === 'approver'
  }

  /**
   * 创建指定节点
   * @param {string} type - 节点类型
   * @param {string} [previousNodeId] - 父节点id
   * @return {object} 节点数据
   * */
  static createNode(type, previousNodeId) {
    const res = JSON.parse(JSON.stringify(nodeConfig[type]))
    res.nodeId = this.idGenerator()
    res.prevId = previousNodeId
    return res
  }

  /**
   * 获取指定节点的父节点（前一个节点）
   * @param {string} prevId - 父节点id
   * @param {object} processData - 流程图全部数据
   * @return {object} 父节点
   * */
  static getPreviousNode(prevId, processData) {
    if (processData.nodeId === prevId) {
      return processData
    }
    if (processData.childNode) {
      const r1 = this.getPreviousNode(prevId, processData.childNode)
      if (r1) {
        return r1
      }
    }
    // 分支节点集合
    if (processData.conditionNodes) {
      for (let c of processData.conditionNodes) {
        let r2 = this.getPreviousNode(prevId, c)
        if (r2) {
          return r2
        }
      }
    }
  }

  /**
   * 删除节点
   * @param {object} nodeData - 被删除节点的数据
   * @param {object} processData - 流程图的所有节点数据
   * @param {boolean} [checkEmpty = true]
   * */
  static deleteNode(nodeData, processData, checkEmpty = true) {
    let prevNode = this.getPreviousNode(nodeData.prevId, processData)
    if (checkEmpty && prevNode.type === 'empty') {
      // 条件节点
      if (this.isConditionNode(nodeData)) {
        const willDelBranch = prevNode.conditionNodes.length === 2
        const target = willDelBranch ? prevNode : nodeData
        this.deleteNode(target, processData, willDelBranch)
      } else {
        if (isEmptyArray(prevNode.conditionNodes)) {
          this.deleteNode(prevNode, processData)
        }
        this.deleteNode(nodeData, processData, false)
      }
      return
    }

    function concatChild(prev, delNode) {
      prev.childNode = delNode.childNode
      if (isEmptyArray(prev.conditionNodes)) {
        prev.conditionNodes = delNode.conditionNodes
      }
      if (prev.childNode) {
        prev.childNode.prevId = prev.nodeId
      }
    }

    // 条件分支
    if (this.isConditionNode(nodeData)) {
      // 取到父节点上存储的所有条件分支数据
      const cons = prevNode.conditionNodes
      const index = cons.findIndex(c => c.nodeId === nodeData.nodeId)
      // 两个以上的条件分支直接删除（不含只有两条分支的情况）
      if (cons.length > 2) {
        cons.splice(index, 1)
      } else {
        /*
         * +!index !0 -> true, +ture -> 1
         * +!index !1 -> false, +false -> 0
         * 当 index === 1 返回0, 当 index === 0 返回1
         * */

        const anotherCon = cons[+!index]
        // 当分支的数量小于等于2时进行删除 直接把分支字段删除掉
        // 删除父节点上存储分支节点的信息
        delete prevNode.conditionNodes
        // 删除后 会把另一条分支上存储的节点移动到上一个节点的childNode下
        if (prevNode.childNode) {
          let endNode = anotherCon
          // 取到分支节点上存储的非条件节点的最后一个节点信息
          while (endNode.childNode) {
            endNode = endNode.childNode
          }
          // 把父节点上存储的子节点放到分支节点中最后一个子节点下
          endNode.childNode = prevNode.childNode
          endNode.childNode.prevId = endNode.nodeId
        }
        concatChild(prevNode, anotherCon)
        if (prevNode.childNode && prevNode.childNode.type === 'empty') {
          this.deleteNode(prevNode.childNode, prevNode)
        }
      }
      // 重新编排优先级
      cons.forEach((c, i) => (c.properties.priority = i))
    }
    concatChild(prevNode, nodeData)
  }

  /**
   * 添加审计节点（普通节点 approver）
   * @param {object} data - 目标节点数据，在改数据节点之后添加审计节点
   * @param {boolean} isBranchAction - 目标节点数据是否是分支节点
   * @param {object} newChildNode - 传入的新的节点 用户操作均为空 删除操作/添加抄送人会传入该参数 以模拟添加节点
   * */
  static addApprovalNode(data, isBranchAction, newChildNode) {
    let oldChildNode = data.childNode
    newChildNode = newChildNode || this.createNode('approver', data.nodeId)
    data.childNode = newChildNode

    if (oldChildNode) {
      newChildNode.childNode = oldChildNode
      oldChildNode.prevId = newChildNode.nodeId
    }

    // 当前节点上的条件节点信息
    let conditionNodes = data.conditionNodes
    // 如果当前节点是普通节点 且当前节点存在分支节点信息 需要把分支节点上的父级节点Id改成当前新创建的节点的id

    if (Array.isArray(conditionNodes) && !isBranchAction && conditionNodes.length) {
      newChildNode.conditionNodes = conditionNodes.map(v => ({
        ...v,
        prevId: newChildNode.nodeId
      }))
      // 删除原始节点上的分支节点信息 上一步已经把原始节点上的分支节点信息存储到了当前节点的下一个节点身上
      delete data.conditionNodes
    }

    if (
      oldChildNode &&
      oldChildNode.type === 'empty' &&
      newChildNode.type !== 'empty' &&
      oldChildNode.conditionNodes.length === 0
    ) {
      this.deleteNode(oldChildNode, data)
    }
  }

  /**
   * 添加空节点
   * @param {object} data - 空节点的父级节点
   * @return {object} emptyNode - 空节点数据
   * */
  static addEmptyNode(data) {
    const emptyNode = this.createNode('empty', data.nodeId)
    this.addApprovalNode(data, true, emptyNode)
    return emptyNode
  }

  // 添加抄送节点
  static addCopyNode(data, isBranchAction) {
    // 复用addApprovalNode  因为抄送人和审批人基本一致
    this.addApprovalNode(data, isBranchAction, this.createNode('copy', data.nodeId))
  }

  /**
   * 添加条件节点 condition 通过点击【添加条件】按钮进入该操作
   * @param {object} data - 目标节点所在分支数据，在该分支最后添加条件节点
   * */
  static appendConditionNode(data) {
    const conditions = data.conditionNodes
    const node = this.createNode('condition', data.nodeId)
    const defaultNodeIndex = conditions.findIndex(node => node.properties.isDefault)
    node.properties.priority = conditions.length
    if (defaultNodeIndex > -1) {
      // 如果节点中存在默认节点（"兜底条件"）
      // 将新节点插入到最后一个节点的前面
      conditions.splice(-1, 0, node)
      // 更新优先级
      node.properties.priority = conditions.length - 2
      conditions[conditions.length - 1].properties.priority = conditions.length - 1
    } else {
      // 不存在默认条件 说明条件节点上都设置完毕了条件信息
      // 直接将新创建的节点插入到最后面
      conditions.push(node)
    }
  }

  /**
   * 添加条件分支 branch - add conditions
   * @param {object} data - 目标节点所在节点数据，在该节点最后添加分支节点
   * @param {boolean} isBottomBtnOfBranch -
   * */
  static appendBranch(data, isBottomBtnOfBranch) {
    let nodeData = data

    // 由于conditionNodes是数组 不能添加下级分支 故在两个分支树之间添加一个不会显示的正常节点 用来承载分支节点的数据
    // 当前节点上已经存在分支数据
    if (Array.isArray(data.conditionNodes) && data.conditionNodes.length) {
      if (isBottomBtnOfBranch) {
        // 添加一个分支节点（空节点）作为新分支的父节点
        nodeData = this.addEmptyNode(nodeData)
      } else {
        const emptyNode = this.addEmptyNode(nodeData)
        emptyNode.conditionNodes = nodeData.conditionNodes
        emptyNode.conditionNodes.forEach(v => {
          v.prevId = emptyNode.nodeId
        })
      }
    }
    const conditionNodes = [
      this.createNode('condition', nodeData.nodeId),
      this.createNode('condition', nodeData.nodeId)
    ].map((v, i) => {
      v.properties.title += i + 1
      v.properties.priority = i
      return v
    })
    nodeData.conditionNodes = conditionNodes
  }

  /**
   * 重设节点优先级（条件节点）
   * @param {object} currentNode - 当前节点
   * @param {number} oldPriority - 替换前的优先级（在数组中的顺序）
   * @param {node} processData - 整个流程图节点数据
   * */
  static resortPrioByCNode(currentNode, oldPriority, processData) {
    // 当前节点为默认节点 取消修改优先级
    if (currentNode.properties.isDefault) {
      currentNode.properties.isDefault = oldPriority
      return
    }
    const prevNode = this.getPreviousNode(currentNode.prevId, processData)
    const newPriority = currentNode.properties.priority
    // 替换节点为默认节点 取消修改优先级
    if (prevNode.conditionNodes[newPriority].properties.isDefault) {
      currentNode.properties.priority = oldPriority
      return
    }
    let delNode = prevNode.conditionNodes.splice(newPriority, 1, currentNode)[0]
    delNode.properties.priority = oldPriority
    prevNode.conditionNodes[oldPriority] = delNode
  }

  /**
   * 提升条件节点优先级 排序在前
   * @param {object} data - 目标节点数据
   * @param {object} processData - 流程图的所有节点数据
   * */
  static increasePriority(data, processData) {
    if (data.properties.isDefault) {
      // 默认节点不能修改优先级
      return
    }
    // 分支节点数据 包含该分支所有的条件节点
    const prevNode = this.getPreviousNode(data.prevId, processData)
    const branchData = prevNode.conditionNodes
    const index = branchData.findIndex(v => v === data)
    if (index) {
      // 和前一个数组项交换位置 Array.prototype.splice会返回包含被删除的项的集合（数组）
      branchData[index - 1].properties.priority = index
      branchData[index].properties.priority = index - 1
      branchData[index - 1] = branchData.splice(index, 1, branchData[index - 1])[0]

      /*
       * 示例：要求交换 a与b
       *   var arr = ['a', 'b', 'c']
       *   arr[0] = arr.splice(1, 1, arr[0])[0]
       *   // arr => ['b', 'a', 'c']
       * */
    }
  }

  /**
   * 降低条件节点优先级 排序在后
   * @param {object} data - 目标节点数据
   * @param {object} processData - 流程图所有节点数据
   * */
  static decreasePriority(data, processData) {
    // 分支节点数据 包含该分支所有的条件节点
    const prevNode = this.getPreviousNode(data.prevId, processData)
    const branchData = prevNode.conditionNodes
    const index = branchData.findIndex(v => v.nodeId === data.nodeId)
    if (index < branchData.length - 1) {
      const nextNode = branchData[index + 1]
      if (nextNode.properties.isDefault) {
        // 默认节点不能修改优先级
        return
      }
      nextNode.properties.priority = index
      branchData[index].properties.priority = index + 1
      branchData[index + 1] = branchData.splice(branchData, 1, branchData[index + 1])[0]
    }
  }

  /**
   * 当有其他条件节点设置条件后 检查并设置最后一个节点为默认节点
   * @param {object} currentNode - 当前节点
   * @param {object} processData - 整个流程题节点数据或父级节点数据
   * */
  static setDefaultCondition(currentNode, processData) {
    const DEFAULT_TEXT = '其他情况进入此流程'
    const conditions = this.getPreviousNode(currentNode.prevId, processData).conditions

    const hasCondition = node =>
      node.properties && (node.properties.initiator || !isEmptyArray(node.properties.conditions))

    const clearDefault = node => {
      node.properties.isDefault = false
      node.content === DEFAULT_TEXT && (node.content = '请设置条件')
    }
    const setDefault = node => {
      node.properties.isDefault = true
      node.content = DEFAULT_TEXT
    }

    let count = 0
    // 排除数组最后一项
    conditions.slice(0, -1).forEach(node => {
      hasCondition(node) && count++
      clearDefault(node)
    })
    const lastNode = conditions[conditions.length - 1]
    count > 0 && !hasCondition(lastNode) ? setDefault(lastNode) : clearDefault(lastNode)
  }

  /**
   * 校验单个节点必填项完整性
   * @param {object} node - 节点数据
   * */
  static checkNode(node) {
    // 抄送人应该可以默认自选
    let valid = true
    const props = node.properties
    this.isStartNode(node) && !props.initiator && (valid = false)

    this.isConditionNode(node) &&
      !props.isDefault &&
      !props.initiator &&
      isEmptyArray(props.conditions) &&
      (valid = false)

    const customSettings = ['myself', 'optional', 'director']
    this.isApproverNode(node) &&
      !customSettings.includes(props.assigneeType) &&
      isEmptyArray(props.approvers) &&
      (valid = false)

    return valid
  }

  /**
   * 判断所有节点是否信息完整
   * @param {object} processData - 整个流程图数据
   * @return {boolean}
   * */
  static checkAllNode(processData) {
    let valid = true

    function loop(node, callback) {
      !this.checkNode(node) && callback()
      if (node.childNode) {
        loop(node.childNode, callback)
      }
      if (!isEmptyArray(node.conditionNodes)) {
        node.conditionNodes.forEach(v => loop(v, callback))
      }
    }
    loop(processData, () => (valid = false))

    return valid
  }
}

// 添加模拟数据
export function getMockFlowData() {
  const startNode = NodeUtils.createNode('start')
  startNode.childNode = NodeUtils.createNode('approver', startNode.nodeId)
  return startNode
}
